package com.foruo.demo.redis.lock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * redis锁工具类
 * @author GaoYuan
 * @date 2018/9/17 下午9:06
 */
public class RedisLock {

    /** key */
    static String lockKey = "lock";

    public static void main(String[] args){
        System.out.println("开始");
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        MyThread myThread = new MyThread();
        executorService.submit(myThread);
        executorService.submit(myThread);
        executorService.submit(myThread);
        executorService.shutdown();

//        /** 模拟三组线程 */
//        MyThread myThread = new MyThread();
//        Thread thread1 = new Thread(myThread);
//        Thread thread2 = new Thread(myThread);
//        Thread thread3 = new Thread(myThread);
//        thread1.start();
//        thread2.start();
//        thread3.start();
    }

    /**
     * 获取锁
     * 利用set key value [EX seconds] [PX milliseconds] [NX|XX] 命令实现锁机制
     * @author GaoYuan
     * @date 2018/9/18 上午8:42
     */
    public static String tryLock(Jedis jedis, int timeout) throws Exception{
        if(timeout == 0){
            timeout = 5000;
        }
        String returnId = null;
        // 生成随机标识
        String id = UUID.randomUUID().toString();
        // 设置锁超时10秒
        int lockExpireMs = 10000;
        long startTime = System.currentTimeMillis();
        // 超时时间内循环获取
        while ((System.currentTimeMillis() - startTime) < timeout){
            String result = jedis.set(lockKey, id, "NX", "PX", lockExpireMs);
            if(result != null){
                returnId = id;
                break;
            }
            TimeUnit.MILLISECONDS.sleep(100);
        }
        if(returnId == null){
            // 获取锁超时，抛出异常
            throw new Exception("获取锁超时");
        }
        // 将set的值返回，用于后续的解锁
        return returnId;
    }


    /**
     * 释放锁 - 利用redis的watch + del
     * @author GaoYuan
     * @date 2018/9/18 上午8:42
     */
    public static boolean unLock(Jedis jedis, String id){
        boolean result = false;
        while(true){
            if(jedis.get(lockKey) == null){
                return false;
            }
            // 配置监听
            jedis.watch(lockKey);
            // 这里确保是加锁者进行解锁
            if(id!=null && id.equals(jedis.get(lockKey))){
                Transaction transaction = jedis.multi();
                transaction.del(lockKey);
                List<Object> results = transaction.exec();
                if(results == null){
                    continue;
                }
                result = true;
            }
            // 释放监听
            jedis.unwatch();
            break;
        }
        return result;
    }

    /**
     * 释放锁 - 利用lua脚本
     * @author GaoYuan
     * @date 2018/9/21 下午12:36
     */
    public static boolean unLockByLua(Jedis jedis, String id){
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(id));
        if (Objects.equals(1, result)) {
            return true;
        }
        return  false;
    }

}



class MyThread implements Runnable{
    int i = 0;

    @Override
    public void run() {
        try {
            for(int j=0;j<10;j++){
                Jedis jedis = new Jedis(JedisConfig.HOST, JedisConfig.PORT);
                try {
                    // 尝试获取锁，有超时时间
                    String id = RedisLock.tryLock(jedis,5000);
                    i = i + 1;
                    // 这里延时，为了让其他线程进行干扰（当然，加锁就不会有干扰）
                    TimeUnit.MILLISECONDS.sleep(1000);
                    i = i - 1;
                    // 加锁后，期望值 i=0
                    System.out.println("i=" + i);
                    // 释放锁
                    RedisLock.unLock(jedis, id);
                }catch (Exception e){
                    // e.printStackTrace();
                    System.out.println("获取锁超时");
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}